﻿// <copyright file="ClrMethod.cs" company="Joe Chung">
// Copyright © 2008 by Joe Chung.
// </copyright>
namespace NScheme
{
    using System;
    using System.Reflection;

    public sealed class ClrMethod : NScheme.Procedure
    {
        public Type[] argClasses;

        public MethodInfo method;

        public bool isStatic;

        public ClrMethod(string methodName, object targetClassName, object argClassNames)
        {
            this.name = targetClassName + "." + methodName;
            try
            {
                this.argClasses = classArray(argClassNames);
                method = toClass(targetClassName).GetMethod(methodName, this.argClasses);
                isStatic = method.IsStatic;
            }
            catch (TypeLoadException)
            {
                error("Bad class, can't get method: " + this.name);
            }
            catch (MissingMethodException)
            {
                error("Can't get method: " + this.name);
            }
        }

        public override object apply(Scheme interpreter, object args)
        {
            return isStatic ? method.Invoke(null, toArray(args)) : method.Invoke(first(args), toArray(rest(args)));
        }

        public object[] toArray(object args)
        {
            int n = length(args);
            int diff = n - argClasses.Length;
            if (diff != 0)
            {
                error(Math.Abs(diff).ToString() + " too " + (diff > 0 ? "many" : "few") + " args to " + name);
            }

            object[] array = new object[n];
            for (int i = 0; i < n && i < argClasses.Length; i++)
            {
                if (argClasses[i] == typeof(int))
                {
                    array[i] = (int)num(first(args));
                }
                else if (argClasses[i] == typeof(string))
                {
                    array[i] = stringify(first(args), false);
                }
                else
                {
                    array[i] = first(args);
                }

                args = rest(args);
            }

            return array;
        }

        public static Type toClass(object arg)
        {
            if (arg is Type)
            {
                return (Type)arg;
            }

            arg = stringify(arg, false);

            if (arg as string == "void")
            {
                return typeof(void);
            }
            else if (arg as string == "boolean")
            {
                return typeof(bool);
            }
            else if (arg as string == "char")
            {
                return typeof(char);
            }
            else if (arg as string == "byte")
            {
                return typeof(byte);
            }
            else if (arg as string == "short")
            {
                return typeof(short);
            }
            else if (arg as string == "int")
            {
                return typeof(int);
            }
            else if (arg as string == "long")
            {
                return typeof(long);
            }
            else if (arg as string == "float")
            {
                return typeof(float);
            }
            else if (arg as string == "double")
            {
                return typeof(double);
            }
            else
            {
                return Type.GetType((string)arg);
            }
        }

        public static Type[] classArray(object args)
        {
            int n = length(args);
            Type[] array = new Type[n];
            for (int i = 0; i < n; i++)
            {
                array[i] = toClass(first(args));
                args = rest(args);
            }

            return array;
        }
    }
}